package flumsynth;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 * PSong
 * <p/>
 * User: druttis Date: 2012-01-03 Time: 19:10
 */
public class PSong {
	private int numTracks = 8;
	private int seqLength = 32;
	private int patLength = 32;
	private int ticksPerBeat = 4;

	private final byte[][] ins = new byte[128][32];
	private final byte[][] seq = new byte[128][128];
	private final byte[][][] pat = new byte[128][128][128];

	private final PPlayer player;
	private int caretColumn;

	public PSong(final PPlayer player) {
		this.player = player;
		for (int track = 0; track < 128; track++) {
			setInsData(track, PPlayer.OSC1_TYPE, 1);
			setInsData(track, PPlayer.OSC1_PITCH, 0);
			setInsData(track, PPlayer.OSC1_FINE, -2);
			setInsData(track, PPlayer.OSC2_TYPE, 1);
			setInsData(track, PPlayer.OSC1_OSC2_FM, 0);
			setInsData(track, PPlayer.OSC1_OSC2_PM, 0);
			setInsData(track, PPlayer.OSC2_PITCH, 0);
			setInsData(track, PPlayer.OSC2_FINE, 2);
			setInsData(track, PPlayer.OSC_MIX, 0);
			setInsData(track, PPlayer.OSC_SYNC, 1);
			setInsData(track, PPlayer.ENV_ATTACK, 1);
			setInsData(track, PPlayer.ENV_DECAY, 8);
			setInsData(track, PPlayer.ENV_SUSTAIN, 56);
			setInsData(track, PPlayer.ENV_RELEASE, 8);
			setInsData(track, PPlayer.ENV_OSC_FM, 0);
			setInsData(track, PPlayer.ENV_FILTER_FM, 0);
			setInsData(track, PPlayer.LFO_RATE, 0);
			setInsData(track, PPlayer.LFO_OSC_FM, 0);
			setInsData(track, PPlayer.LFO_FILTER_FM, 0);
			setInsData(track, PPlayer.FILTER_TYPE, 0);
			setInsData(track, PPlayer.FILTER_FREQ, 64);
			setInsData(track, PPlayer.FILTER_RES, 64);
			setInsData(track, PPlayer.DIST_GAIN, 0);
			setInsData(track, PPlayer.DIST_THRESHOLD, 64);
			setInsData(track, PPlayer.VOLUME, 32);
			setInsData(track, PPlayer.PAN, 0);
			setInsData(track, PPlayer.DELAY_LEFT_DUR, 2);
			setInsData(track, PPlayer.DELAY_RIGHT_DUR, 3);
			setInsData(track, PPlayer.DELAY_FEEDBACK, 8);
			setInsData(track, PPlayer.DELAY_MIX, 8);
		}
	}

	public int getNumTracks() {
		return numTracks;
	}

	public void setNumTracks(final int numTracks) {
		this.numTracks = numTracks;
		update();
	}

	public int getInsData(final int track, final int pos) {
		return ins[track][pos];
	}

	public void setInsData(final int track, final int pos, final int val) {
		ins[track][pos] = (byte) val;
		update();
	}

	public int getSeqLength() {
		return seqLength;
	}

	public void setSeqLength(final int seqLength) {
		this.seqLength = seqLength;
		update();
	}

	public int getSeqData(final int track, final int pos) {
		return seq[track][pos];
	}

	public void setSeqData(final int track, final int pos, final int val) {
		seq[track][pos] = (byte) val;
		update();
	}

	public int getPatLength() {
		return patLength;
	}

	public void setPatLength(final int patLength) {
		this.patLength = patLength;
		update();
	}

	public int getPatData(final int track, final int ptrn, final int pos) {
		return pat[track][ptrn][pos];
	}

	public void setPatData(final int track, final int ptrn, final int pos, final int val) {
		pat[track][ptrn][pos] = (byte) val;
		update();
	}

	public int getTicksPerBeat() {
		return ticksPerBeat;
	}

	public void setTicksPerBeat(final int ticksPerBeat) {
		this.ticksPerBeat = ticksPerBeat;
	}

	public int getCaretColumn() {
		return caretColumn;
	}

	public void setCaretColumn(final int caretColumn) {
		this.caretColumn = caretColumn;
	}

	private void update() {
		// PPlayer.instance.stop();
		PPlayer.NUM_TRACKS = numTracks;
		PPlayer.SEQ_LENGTH = seqLength;
		PPlayer.PAT_LENGTH = patLength;
		for (int track = 0; track < numTracks; track++) {
			final int tofs = track * PPlayer.TRACK_SIZE;
			for (int ins = 0; ins < 32; ins++) {
				player.song[tofs + ins] = this.ins[track][ins];
			}
			for (int seq = 0; seq < seqLength; seq++) {
				final int sofs = tofs + PPlayer.SEQ_BEGIN + seq;
				player.song[sofs] = this.seq[track][seq];
				final int ptrn = this.seq[track][seq];
				final int pofs = tofs + PPlayer.PAT_BEGIN + ptrn * PPlayer.PAT_LENGTH;
				for (int pat = 0; pat < patLength; pat++) {
					player.song[pofs + pat] = this.pat[track][ptrn][pat];
				}
			}
		}
	}

	public void write(final OutputStream out) throws IOException {
		final GZIPOutputStream gzip = new GZIPOutputStream(out);
		gzip.write(numTracks);
		gzip.write(seqLength);
		gzip.write(patLength);
		for (int track = 0; track < numTracks; track++) {
			final Set<Integer> visited = new HashSet<Integer>();
			gzip.write(ins[track]);
			gzip.write(seq[track], 0, seqLength);
			for (int pos = 0; pos < seqLength; pos++) {
				final int ptn = seq[track][pos];
				if (!visited.contains(ptn)) {
					visited.add(ptn);
					gzip.write(pat[track][ptn], 0, patLength);
				}
			}
		}
		gzip.close();
	}

	public void read(final InputStream in) throws IOException {
		final GZIPInputStream gzip = new GZIPInputStream(in);
		numTracks = gzip.read();
		seqLength = gzip.read();
		patLength = gzip.read();
		for (int track = 0; track < numTracks; track++) {
			final Set<Integer> visited = new HashSet<Integer>();
			gzip.read(ins[track]);
			gzip.read(seq[track], 0, seqLength);
			for (int pos = 0; pos < seqLength; pos++) {
				final int ptn = seq[track][pos];
				if (!visited.contains(ptn)) {
					visited.add(ptn);
					gzip.read(pat[track][ptn], 0, patLength);
				}
			}
		}
		update();
	}
}
